﻿/*--------------------------------------------------------------------------------
 * Adaptive Intelligence - Extended Code Document Object Model
 * Version 2.10
 * 
 * VS Code Parser Class
 *      Provides an implementation for parsing C# or VB code into CodeDOM graphs.
 *------------------------------------------------------------------------------*/

/*--------------------------------------------------------------------------------
 * System Imports
 *------------------------------------------------------------------------------*/
using System;
using System.IO;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Windows.Forms;

using EnvDTE;
using Adaptive.Foundation;
using Adaptive.Foundation.CodeDom.Native;
using Adaptive.Foundation.CodeDom.Properties;
using Microsoft.Win32;

namespace Adaptive.Foundation.CodeDom.Parsers
{
    /// <summary>
    /// Provides an implementation for parsing C# or VB code into CodeDOM graphs.
    /// </summary>
    public static class VsCodeParser
    {
        /*--------------------------------------------------------------------------------
         * Private Static Members
         *------------------------------------------------------------------------------*/
        #region Private Static Members
        private static string _projectTemplateFileName = string.Empty;
        private static string _classModuleFileName = string.Empty;
        private static string _temporaryPath = Path.GetTempPath();
        private static string _previousPath = string.Empty;
        private static string _subFolderName = string.Empty;
        #endregion

        /*--------------------------------------------------------------------------------
         * Public Methods / Functions
         *------------------------------------------------------------------------------*/
        #region Public Methods / Functions
        /// <summary>
        /// Creates the COM instance of a Visual Studio solution.
        /// </summary>
        /// <param name="version">
        /// A <see cref="VisualStudioVersions"/> enumerated value indicating which version to use.
        /// </param>
        /// <returns>
        /// A <see cref="Solution"/> instance if successful; otherwise, <b>null</b>.
        /// </returns>
        /// <exception cref="NotSupportedException">
        /// This exception is thrown if the specified version of Visual Studio is not installed.
        /// </exception>
        public static Solution CreateVisualStudioSolution(VisualStudioVersions version)
        {
            string objectName = string.Empty;           //Name of the object instance.
            Solution returnValue = null;                //Return value.

            if (!IsVisualStudioVersionInstalled(version))
                throw new NotSupportedException("The specified version of Visual Studio is not installed.");

            objectName = GetObjectNameForVersion(version);
            returnValue = CreateSolutionInstance(objectName);

            return returnValue;
        }
        /// <summary>
        /// Gets the project file extension for the specified VS language.
        /// </summary>
        /// <param name="language">
        /// A <see cref="VisualStudioLanguages"/> enumerated value indicating the selected language.
        /// </param>
        /// <returns>
        /// A string containing the extension.
        /// </returns>
        public static string GetProjectFileExtensionForLanguage(VisualStudioLanguages language)
        {
            string returnValue = null;

            switch (language)
            {
                case VisualStudioLanguages.VisualBasic:
                    returnValue = Resources.VisualBasicProjectExtension;
                    break;

                case VisualStudioLanguages.CSharp:
                    returnValue = Resources.CSharpProjectExtension;
                    break;

                default:
                    returnValue = string.Empty;
                    break;
            }

            return returnValue;
        }
        /// <summary>
        /// Gets the class/module file extension for the specified VS language.
        /// </summary>
        /// <param name="language">
        /// A <see cref="VisualStudioLanguages"/> enumerated value indicating the selected language.
        /// </param>
        /// <returns>
        /// A string containing the extension.
        /// </returns>
        public static string GetFileExtensionForLanguage(VisualStudioLanguages language)
        {
            string returnValue = null;

            switch (language)
            {
                case VisualStudioLanguages.VisualBasic:
                    returnValue = Resources.VisualBasicFileExtension;
                    break;

                case VisualStudioLanguages.CSharp:
                    returnValue = Resources.CSharpFileExtension;
                    break;

                default:
                    returnValue = string.Empty;
                    break;
            }
            return returnValue;
        }
        /// <summary>
        /// Gets the latest version of Visual Studio installed on the local system.
        /// </summary>
        /// <returns>
        /// A <see cref="VisualStudioVersions"/> enumerated value indicating the latest installed version.
        /// </returns>
        public static VisualStudioVersions GetLatestVisualStudioInstalled()
        {
            VisualStudioVersions returnValue = VisualStudioVersions.None;

            //Look for latest version first.
            if (IsVisualStudioVersionInstalled(VisualStudioVersions.Version2008))
                returnValue = VisualStudioVersions.Version2008;

            else if (IsVisualStudioVersionInstalled(VisualStudioVersions.Version2005))
                returnValue = VisualStudioVersions.Version2005;

            else if (IsVisualStudioVersionInstalled(VisualStudioVersions.Version2003))
                returnValue = VisualStudioVersions.Version2003;
            
            else if (IsVisualStudioVersionInstalled(VisualStudioVersions.Version2002))
                returnValue = VisualStudioVersions.Version2002;

            return returnValue;
        }
        /// <summary>
        /// Determines whether Visual Studio is installed on the local system.
        /// </summary>
        /// <returns>
        /// <b>true</b> if Visual Studio is installed; otherwise, <b>false</b>.
        /// </returns>
        public static bool IsVisualStudioInstalled()
        {
            bool returnValue = false;                   //Return value.

            //Look for latest version first.
            returnValue = IsVisualStudioVersionInstalled(VisualStudioVersions.Version2008);
            if (!returnValue)
            {
                returnValue = IsVisualStudioVersionInstalled(VisualStudioVersions.Version2005);
                if (!returnValue)
                {
                    returnValue = IsVisualStudioVersionInstalled(VisualStudioVersions.Version2003);
                    if (!returnValue)
                        returnValue = IsVisualStudioVersionInstalled(VisualStudioVersions.Version2002);
                }
            }

            return returnValue;
        }
        /// <summary>
        /// Determines whether a specific version of Visual Studio is installed on the local system.
        /// </summary>
        /// <param name="version">
        /// The version of Visual Studio to check for.
        /// </param>
        /// <returns>
        /// <b>true</b> if the item is installed; otherwise, <b>false</b>.
        /// </returns>
        public static bool IsVisualStudioVersionInstalled(VisualStudioVersions version)
        {
            bool returnValue = false;                   //Return value.
            string objectName = string.Empty;           //Solution Object name,
            Guid classIdValue = Guid.Empty;             //Class ID value for solution object.

            //Get version name.
            objectName = GetObjectNameForVersion(version);

            //Attempt to retrieve clas ID from registry.
            classIdValue = GetSolutionObjetcClassId(objectName);
            returnValue = (classIdValue != Guid.Empty) ;

            return returnValue;
        }
        /// <summary>
        /// Parses the specified code content into a Code DOM representation.
        /// </summary>
        /// <param name="codeContent">
        /// The text containing the compilable code.
        /// </param>
        /// <param name="language">
        /// A <see cref="VisualStudioLanguages"/> enumerated value indicating the language the
        /// <i>codeContent</i> is written in.
        /// </param>
        /// <returns>
        /// If successful, returns a <see cref="CodeCompileUnit"/> containing the Code DOM graph;
        /// otherwise, returns <b>null</b>.
        /// </returns>
        public static CodeCompileUnit Parse(string codeContent, VisualStudioLanguages language)
        {
            CodeCompileUnit returnValue = null;             //Return value.
            Solution solution = null;                       //Solution object instance.
            VsCodeDomProvider provider = null;              //A Code-DOM provider for parsing.
            ProjectItem item = null;                        //A VS-project item COM instance.
            IServiceProvider service = null;                //Service provider instance.
            MemoryStream stream = null;                     //Memory data container.
            StreamReader reader = null;                     //Text reader for parser.

            //Currently, only VB and C# are supported.
            if ((language != VisualStudioLanguages.CSharp) && (language != VisualStudioLanguages.VisualBasic))
                throw new ArgumentException("Invalid Visual Studio language selected.");

            //Create the solution instance.
            solution = CreateSolutionInstance(GetObjectNameForVersion(GetLatestVisualStudioInstalled()));
            if (solution != null)
            {
                //Generate the code and template files for VS to use.
                item = GenerateEmptyProject(solution, codeContent, language);

                //Try to create the service instance.
                try
                {
                    service = new Microsoft.VisualStudio.Shell.ServiceProvider(item.ContainingProject.Globals.DTE);
                }
                catch
                {
                    service = null;
                }
                if (service != null)
                {
                    try
                    {
                        //Create the code-dom provider based on the language selection.
                        if (language == VisualStudioLanguages.CSharp)
                            provider = new VsCodeDomProvider(service, item, new Microsoft.CSharp.CSharpCodeProvider());
                        else
                            provider = new VsCodeDomProvider(service, item, new Microsoft.VisualBasic.VBCodeProvider());
                    }
                    catch
                    {
                        provider = null;
                    }

                    if (provider != null)
                    {
                        //Pull the contents into memory so they can be read as text.
                        stream = new MemoryStream(System.Text.UnicodeEncoding.Unicode.GetBytes(codeContent));
                        reader = new StreamReader(stream);

                        //Perform parsing.
                        try
                        {
                            returnValue = provider.Parse(reader);
                        }
                        catch
                        {
                            returnValue = null;
                        }

                        //Close files.
                        reader.Close();
                        reader.Dispose();
                        stream.Close();
                        stream.Dispose();

                        provider.Dispose();
                        provider = null;
                    }
                }

                //Delete the temporary files that were used.
                DeleteEmptyContent();
                item = null;
                solution = null;
            }
            else
                throw new Exception("Visual Studio is not installed.");

            return returnValue;
        }
        /// <summary>
        /// Parses the specified code file into a Code DOM representation.
        /// </summary>
        /// <param name="codeFileName">
        /// The name of the file containing the compilable code.
        /// </param>
        /// <param name="language">
        /// A <see cref="VisualStudioLanguages"/> enumerated value indicating the language the
        /// <i>codeContent</i> is written in.
        /// </param>
        /// <returns>
        /// If successful, returns a <see cref="CodeCompileUnit"/> containing the Code DOM graph;
        /// otherwise, returns <b>null</b>.
        /// </returns>
        public static CodeCompileUnit ParseFile(string codeFileName, VisualStudioLanguages language)
        {
            FileStream file = null;             //File instance.
            StreamReader reader = null;         //Reader instance.
            string dataContent = null;          //Data.
            CodeCompileUnit returnValue = null; //Return value.

            //Attempt to open the file.
            try
            {
                file = new FileStream(codeFileName, FileMode.Open, FileAccess.Read);
            }
            catch
            {
                file = null;
            }

            if (file != null)
            {
                reader = new StreamReader(file);
                try
                {
                    dataContent = reader.ReadToEnd();
                }
                catch
                {
                    dataContent = null;
                }
                reader.Close();
                reader.Dispose();

                file.Close();
                file.Dispose();
            }

            if (dataContent != null)
                returnValue = Parse(dataContent, language);

            dataContent = null;
            return returnValue;
        }
        #endregion

        /*--------------------------------------------------------------------------------
         * Private Methods / Functions
         *------------------------------------------------------------------------------*/
        #region Private Methods / Functions
        /// <summary>
        /// Creates the VS Solution COM object instance specified by the object name.
        /// </summary>
        /// <param name="solutionObjectKey">The TypeID value for the object.</param>
        /// <returns>
        /// A <see cref="Solution"/> instance if successful; otherwise, <b>null</b>.
        /// </returns>
        private static Solution CreateSolutionInstance(string solutionObjectKey)
        {
            Solution returnValue = null;                //Return value.
            Type solutionObjectType = null;             //Type of object.
            object instanceValue = null;                //Raw object instance.

            //Attempt to get the type value.
            try
            {
                solutionObjectType = Type.GetTypeFromProgID(solutionObjectKey);
            }
            catch
            {
                solutionObjectType = null;
            }

            if (solutionObjectType != null)
            {
                //Attempt to create the instance.
                try
                {
                    instanceValue = Activator.CreateInstance(solutionObjectType, true);
                }
                catch
                {
                    instanceValue = null;
                }

                //Re-cast the return value.
                if (instanceValue != null)
                    returnValue = (Solution)instanceValue;
            }
            return returnValue;
        }
        /// <summary>
        /// Generates the project and project item objects for the specified content and 
        /// Visual Studio language.
        /// </summary>
        /// <param name="newInstance">
        /// A <see cref="Solution"/> instance.
        /// </param>
        /// <param name="content">
        /// The content being parsed.
        /// </param>
        /// <param name="language">
        /// A <see cref="VisualStudioLanguages"/> enumerated value indicating the language to use,
        /// </param>
        /// <returns>
        /// A <see cref="ProjectItem"/> instance to use in parsing.
        /// </returns>
        private static ProjectItem GenerateEmptyProject(Solution newInstance, string content, VisualStudioLanguages language)
        {
            Project newProject = null;                      //Project instance.
            ProjectItem returnValue = null;                 //Return value.
            string tempFileName = string.Empty;             //Temporary file name.

            //Create the temporary project template file.
            tempFileName = Path.GetTempFileName();
            _projectTemplateFileName = tempFileName + GetProjectFileExtensionForLanguage(language);
            File.Move(tempFileName, _projectTemplateFileName);
            tempFileName = string.Empty;

            //Create the temporary project class/module file.
            tempFileName = Path.GetTempFileName();
            _classModuleFileName = tempFileName + GetFileExtensionForLanguage(language);
            File.Move(tempFileName, _classModuleFileName);
            tempFileName = string.Empty;

            //Save the current path and change to the temp path,
            _previousPath = Directory.GetCurrentDirectory();
            Directory.SetCurrentDirectory(_temporaryPath);

            //Create the template to copy from.
            CreateVsProjectTemplateFile(language, _projectTemplateFileName);

            //Create the class/module name.
            CreateBlankVsFile(language, _classModuleFileName, content);

            //Create structure for VS to create "solution".
            _subFolderName = Path.Combine(_temporaryPath, Resources.TemporaryProjectFolderName);
            CreateVsFolderStructure(_subFolderName);
            
            //Convert the template to a project and copy the file content.
            newProject = newInstance.AddFromTemplate(_projectTemplateFileName, _subFolderName, Resources.TemporaryProjectFolderName, true);
            returnValue = newProject.ProjectItems.AddFromFileCopy(_classModuleFileName);

            return returnValue;
        }
        /// <summary>
        /// Ensures that VS can create the "solution" folder.
        /// </summary>
        /// <param name="path">The path in which the temporary solution will be placed.</param>
        private static void CreateVsFolderStructure(string path)
        {
            if (Directory.Exists(path))
                Directory.Delete(path);
        }
        /// <summary>
        /// Creates the temporary VS Project template file.
        /// </summary>
        /// <param name="language">
        /// A <see cref="VisualStudioLanguages"/> enumerated value indicating the 
        /// language of the project to be created.
        /// </param>
        /// <param name="fileName">
        /// The name of the file to be created.
        /// </param>
        private static void CreateVsProjectTemplateFile(VisualStudioLanguages language, string fileName)
        {
            FileStream file = null;                     //File instance.
            StreamWriter writer = null;                 //Writer instance.

            //Attempt to create the template file.
            try
            {
                file = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.Write);
            }
            catch
            {
                file = null;
            }

            if (file != null)
            {
                writer = new StreamWriter(file);

                //Write content based on language.
                if (language == VisualStudioLanguages.CSharp)
                    writer.WriteLine(Resources.CsProjectTemplateContent);
                else if (language == VisualStudioLanguages.VisualBasic)
                    writer.WriteLine(Resources.VbProjectTemplateContent);

                writer.Close();
                writer.Dispose();
                file.Close();
                file.Dispose();
            }
            writer = null;
            file = null;
        }
        /// <summary>
        /// Creates the code content file.
        /// </summary>
        /// <param name="language">
        /// A <see cref="VisualStudioLanguages"/> enumerated value indicating the 
        /// language of the file to be created.
        /// </param>
        /// <param name="fileName">The name of the file to be created.</param>
        /// <param name="content">The code content to deposit in the new file.</param>
        private static void CreateBlankVsFile(VisualStudioLanguages language, string fileName, string content)
        {
            FileStream file = null;                     //File instance.
            StreamWriter writer = null;                 //Writer instance.

            //Attempt to create the template file.
            try
            {
                file = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.Write);
            }
            catch
            {
                file = null;
            }

            if (file != null)
            {
                writer = new StreamWriter(file);
                writer.WriteLine(content);
                writer.Close();
                writer.Dispose();
                file.Close();
                file.Dispose();
            }
            writer = null;
            file = null;
        }
        /// <summary>
        /// Cleans up the temporary files.
        /// </summary>
        private static void DeleteEmptyContent()
        {
            try
            {
                File.Delete(_classModuleFileName);
            }
            catch { }

            try
            {
                File.Delete(_projectTemplateFileName);
            }
            catch { }

            try
            {
                Directory.Delete(_subFolderName, true);
            }
            catch { }

            //Switch back to original directory and clear memory.
            Directory.SetCurrentDirectory(_previousPath);
            _previousPath = string.Empty;
            _classModuleFileName = string.Empty;
            _projectTemplateFileName = string.Empty;
            _subFolderName = string.Empty;
            _temporaryPath = string.Empty;
        }
        /// <summary>
        /// Gets the GUID value associated with the specified solution object name.
        /// </summary>
        /// <param name="solutionKeyName">
        /// A type ID string identifying a Visual Studio solution object.
        /// </param>
        /// <returns>
        /// The GUID value stored in the CLSID registry key of the specified item, or an empty Guid if 
        /// not found.
        /// </returns>
        private static Guid GetSolutionObjetcClassId(string solutionKeyName)
        {
            RegistryKey softwareClassesKey = null;
            RegistryKey subKey = null;
            string value = string.Empty;
            Guid returnValue = Guid.Empty;

            try
            {
                softwareClassesKey = Registry.LocalMachine.OpenSubKey(Resources.SoftwareClassesRegistryKeyName);
            }
            catch
            {
                softwareClassesKey = null;
            }
            if (softwareClassesKey != null)
            {
                try
                {
                    subKey = softwareClassesKey.OpenSubKey(solutionKeyName + "\\" + Resources.ClassIdRegistryKeyName);
                }
                catch
                {
                    subKey = null;
                }
                if (subKey != null)
                {
                    value = (string)subKey.GetValue(string.Empty);
                    returnValue = new Guid(value);
                    subKey.Close();
                }
                softwareClassesKey.Close();
            }
            return returnValue;
        }
        /// <summary>
        /// Gets the typeiD object name for the specified visual studio version.
        /// </summary>
        /// <param name="version">
        /// A <see cref="VisualStudioVersions"/> enumerated value indicating the version
        /// of Visual Studio to create an object for.
        /// </param>
        /// <returns>
        /// The string ID value used to find the GUID of the class type in the registry.
        /// </returns>
        private static string GetObjectNameForVersion(VisualStudioVersions version)
        {
            string returnValue = string.Empty;

            switch (version)
            {
                case VisualStudioVersions.Version2002:
                    returnValue = Resources.VSSolutionObject70;
                    break;

                case VisualStudioVersions.Version2003:
                    returnValue = Resources.VSSolutionObject71;
                    break;

                case VisualStudioVersions.Version2005:
                    returnValue = Resources.VSSolutionObject80;
                    break;

                case VisualStudioVersions.Version2008:
                    returnValue = Resources.VSSolutionObject90;
                    break;

                default:
                    returnValue = string.Empty;
                    break;
            }
            return returnValue;
        }
        #endregion

    }
}
